﻿using DotNetty.Buffers;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System.Net;

namespace DotNetty.Extensions
{
    public class TcpSocketClient : IAsyncDisposable
    {
        public TcpSocketClient(string serverIp, int serverPort, int timeout = 3)
        {
            _serverIp = serverIp;
            _serverPort = serverPort;
            _timeout = timeout;
        }

        private readonly string _serverIp;

        private readonly int _serverPort;

        private readonly int _timeout;

        public bool UseSsl { get; internal set; }

        public bool TcpNodelay { get; set; } = true;

        public int WorkerGroupCount { get; set; } = 0;

        private IEventLoopGroup group;

        public IChannel Channel { get; internal set; }

        private readonly SemaphoreSlim _slim = new(1, 1);

        private bool _slimDispose = false;

        public event Action<Bootstrap> OnCreateBootstrap;

        public event Action<IChannelPipeline> OnChannelPipeline;

        public event Action OnConnecting;

        public event Action OnConnected;

        public event Action<object> OnMessage;

        public event Action<Exception> OnException;

        public event Action OnDisconnected;

        internal void Connected()
        {
            try
            {
                OnConnected?.Invoke();
            }
            catch { }
        }

        internal void Message(object msg)
        {
            try
            {
                OnMessage?.Invoke(msg);
            }
            catch { }
        }

        internal void Ex(Exception ex)
        {
            try
            {
                OnException?.Invoke(ex);
            }
            catch { }
        }

        internal void Disconnected()
        {
            try
            {
                OnDisconnected?.Invoke();
            }
            catch { }
        }

        private async Task CloseGroupAsync()
        {
            if (group != null)
            {
                try
                {
                    await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
                }
                catch { }
            }
        }

        private async Task ConnectBaseAsync()
        {
            try
            {
                if (WorkerGroupCount <= 0)
                {
                    group = new MultithreadEventLoopGroup();
                }
                else
                {
                    group = new MultithreadEventLoopGroup(WorkerGroupCount);
                }

                var bootstrap = new Bootstrap()
                        .Group(group)
                        .Channel<TcpSocketChannel>()
                        .Option(ChannelOption.TcpNodelay, TcpNodelay)
                        .Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(_timeout))
                        .Handler(new ActionChannelInitializer<ISocketChannel>(ch =>
                        {
                            IChannelPipeline pipeline = ch.Pipeline;
                            OnChannelPipeline?.Invoke(pipeline);

                            var tls = pipeline.FirstOrDefault(f => f.GetType() == typeof(TlsHandler));
                            UseSsl = tls != null;

                            pipeline.AddLast(new TcpClientHandler(this));

                        }));

                OnCreateBootstrap?.Invoke(bootstrap);

                try
                {
                    OnConnecting?.Invoke();
                }
                catch { }

                Channel = await bootstrap.ConnectAsync(IPAddress.Parse(_serverIp), _serverPort);

;                try
                {
                    OnConnected?.Invoke();
                }
                catch { }
            }
            catch (Exception ex)
            {
                await CloseGroupAsync();

                try
                {
                    OnException?.Invoke(ex);
                }
                catch { }

                try
                {
                    OnDisconnected?.Invoke();
                }
                catch { }
            }
        }

        public async Task ConnectAsync()
        {
            if (_slimDispose)
            {
                return;
            }

            try
            {
                var ok = await _slim.WaitAsync(0);
                if (!ok)
                {
                    return;
                }

                await ConnectBaseAsync();

                _slim.Release();
            }
            catch { }
        }

        public async Task WriteAsync(object message)
        {
            await Channel.WriteAsync(message);
        }

        public async Task WriteAsync(IByteBuffer buffer)
        {
            await Channel.WriteAsync(buffer);
        }

        public async Task WriteAsync(byte[] bytes)
        {
            await Channel.WriteAsync(Unpooled.WrappedBuffer(bytes));
        }

        public void Flush()
        {
            Channel.Flush();
        }

        public async Task WriteAndFlushAsync(object message)
        {
            await Channel.WriteAndFlushAsync(message);
        }

        public async Task WriteAndFlushAsync(IByteBuffer buffer)
        {
            await Channel.WriteAndFlushAsync(buffer);
        }

        public async Task WriteAndFlushAsync(byte[] bytes)
        {
            await Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(bytes));
        }

        public async ValueTask DisposeAsync()
        {
            await _slim.WaitAsync();
            _slimDispose = true;

            if (Channel != null)
            {
                try
                {
                    await Channel.CloseAsync();
                    Channel = null;
                }
                catch { }
            }

            await CloseGroupAsync();

            _slim.Release();
            _slim.Dispose();

            GC.SuppressFinalize(this);
        }
    }
}
